package com.hd123.rumba.gwt.widget2.client.dialog;

import java.util.HashMap;
import java.util.Map;

import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasOpenHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ListenerWrapper;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.hd123.rumba.gwt.widget2.client.button.RButton;
import com.hd123.rumba.gwt.widget2.client.misc.RMask;

/**
 * 对话框，可以继承本类创建新的对话框，也可直接使用。
 * <p>
 * 特性：
 * <li>标题栏
 * <li>拖动标题栏可拖动整个对话框移动
 * <li>标题栏右侧有一个可选的关闭按钮
 * <li>对话框下部是一个可选的按钮面板，可显示一系列按钮
 * <li>可设置一个默认获得焦点的按钮，此时用户可直接按回车或空格激活此按钮
 * <li>对话框中部是工作区域，可显示任何控件，用setWidget方法设置
 * <li>对话框显示时在页面背景显示半透明遮罩（可选）
 * <li>ESC键关闭对话框（可选）
 * <p>
 * 使用者可在RDialog的PopupListener中进行对话框关闭后的处理，调用
 * {@link #getClickedButton()}获得被点击的按钮， 如果发现
 * {@link #getClickedButton()}
 * 返回null，则说明用户没有点击任何按钮，是按ESC键关闭的或者是程序关闭的。
 * <p>
 * 如果希望改变按钮的行为，则可调用addButtonClickListener，增加对onClick事件的响应，
 * 甚至可以通过返回false阻止对话框的关闭。
 * 
 * @author LiQi
 * 
 */
@SuppressWarnings("deprecation")
public class RDialog extends PopupPanel implements HasOpenHandlers<PopupPanel> {

  /** 样式 */
  private static final String STYLENAME = "rb-Dialog";
  private static final String STYLENAME_TOPLEFT = "rb-Dialog-topleft";
  private static final String STYLENAME_TOPCENTER = "rb-Dialog-topcenter";
  private static final String STYLENAME_TOPRIGHT = "rb-Dialog-topright";
  private static final String STYLENAME_MIDDLELEFT = "rb-Dialog-middleleft";
  private static final String STYLENAME_MIDDLECENTER = "rb-Dialog-middlecenter";
  private static final String STYLENAME_MIDDLERIGHT = "rb-Dialog-middleright";
  private static final String STYLENAME_BOTTOMLEFT = "rb-Dialog-bottomleft";
  private static final String STYLENAME_BOTTOMCENTER = "rb-Dialog-bottomcenter";
  private static final String STYLENAME_BOTTOMRIGHT = "rb-Dialog-bottomright";

  private static final String STYLENAME_CAPTION = "rb-Dialog-caption";
  private static final String STYLENAME_TOPRIGHTCLOSEBUTTON = "rb-Dialog-topRightCloseButton";
  private static final String STYLENAME_BUTTONSFRAME = "rb-Dialog-buttonsframe";

  /** 整个对话框的根 */
  private Grid root;

  /** 标题面板 */
  private HTML caption;

  /** 右上角的关闭按钮 */
  private Image topRightCloseButton;

  /** 内容框架，包含工作区域和按钮面板框架 */
  private VerticalPanel contentFrame;

  /** 工作区域 */
  private SimplePanel workingArea;

  /** 按钮面板的框架 */
  private HorizontalPanel buttonsFrame;

  /** 按钮面板 */
  private HorizontalPanel buttonsPanel;

  /** 预定义按钮 */
  public static final ButtonConfig BUTTON_OK = new ButtonConfig("确定");
  public static final ButtonConfig BUTTON_CANCEL = new ButtonConfig("取消");
  public static final ButtonConfig BUTTON_YES = new ButtonConfig("是");
  public static final ButtonConfig BUTTON_NO = new ButtonConfig("否");
  public static final ButtonConfig BUTTON_CLOSE = new ButtonConfig("关闭");
  public static final ButtonConfig BUTTON_YESTOALL = new ButtonConfig("全是");
  public static final ButtonConfig BUTTON_NOTOALL = new ButtonConfig("全否");
  public static final ButtonConfig BUTTON_ABORT = new ButtonConfig("退出");
  public static final ButtonConfig BUTTON_RETRY = new ButtonConfig("重试");
  public static final ButtonConfig BUTTON_IGNORE = new ButtonConfig("忽略");
  public static final ButtonConfig BUTTON_CLEAR = new ButtonConfig("清除");

  public static final ButtonConfig[] BUTTONS_OKCANCEL = new ButtonConfig[] {
      BUTTON_OK, BUTTON_CANCEL };
  public static final ButtonConfig[] BUTTONS_YESNO = new ButtonConfig[] {
      BUTTON_YES, BUTTON_NO };
  public static final ButtonConfig[] BUTTONS_YESNOCANCEL = new ButtonConfig[] {
      BUTTON_YES, BUTTON_NO, BUTTON_CANCEL };
  public static final ButtonConfig[] BUTTONS_ABORTRETRYIGNORE = new ButtonConfig[] {
      BUTTON_ABORT, BUTTON_RETRY, BUTTON_IGNORE };
  public static final ButtonConfig[] BUTTONS_RETRYCANCEL = new ButtonConfig[] {
      BUTTON_RETRY, BUTTON_CANCEL };

  /** 按钮集合 */
  private ButtonConfig[] buttonConfigs;

  /** 按钮定义和真实按钮的映射 */
  private Map buttonMap = new HashMap();

  /** 对话框显示时获得焦点的按钮 */
  private ButtonConfig focusedButton = null;

  /** 被点击的按钮 */
  private ButtonConfig clickedButton = null;

  /** RDialogButtonClickListener集合 */
  private RDialogButtonClickListenerCollection buttonClickListeners;

  /** 是否显示遮罩 */
  private boolean showMask = true;

  /** 是否支持用ESC键关闭对话框 */
  private boolean escKeyToHide = false;

  public RDialog() {
    this(false, true);
  }

  /**
   * @see {@link com.google.gwt.user.client.ui.PopupPanel#PopupPanel(boolean, boolean)}
   * @param autoHide
   * @param modal
   */
  public RDialog(boolean autoHide, boolean modal) {
    super(autoHide, modal);
    setAnimationEnabled(true);
    buttonClickListeners = new RDialogButtonClickListenerCollection();
    buildUI();
  }

  /**
   * 构造界面
   * 
   */
  protected void buildUI() {
    setStyleName(STYLENAME);

    // 用一个表格作为框架
    root = new Grid(3, 3);
    root.setCellPadding(0);
    root.setCellSpacing(0);

    root.getCellFormatter().setStyleName(0, 0, STYLENAME_TOPLEFT);
    root.getCellFormatter().setStyleName(0, 1, STYLENAME_TOPCENTER);
    root.getCellFormatter().setStyleName(0, 2, STYLENAME_TOPRIGHT);
    root.getCellFormatter().setStyleName(1, 0, STYLENAME_MIDDLELEFT);
    root.getCellFormatter().setStyleName(1, 1, STYLENAME_MIDDLECENTER);
    root.getCellFormatter().setStyleName(1, 2, STYLENAME_MIDDLERIGHT);
    root.getCellFormatter().setStyleName(2, 0, STYLENAME_BOTTOMLEFT);
    root.getCellFormatter().setStyleName(2, 1, STYLENAME_BOTTOMCENTER);
    root.getCellFormatter().setStyleName(2, 2, STYLENAME_BOTTOMRIGHT);

    // 标题栏，包含标题内容和关闭按钮
    HorizontalPanel captionBar = new HorizontalPanel();
    captionBar.setWidth("100%");
    root.setWidget(0, 1, captionBar);

    // 标题内容
    caption = new HTML("&nbsp;");
    CaptionHandler captionHandler = new CaptionHandler();
    caption.addMouseDownHandler(captionHandler);
    caption.addMouseMoveHandler(captionHandler);
    caption.addMouseUpHandler(captionHandler);
    caption.setStyleName(STYLENAME_CAPTION);
    captionBar.add(caption);
    captionBar.setCellWidth(caption, "100%");

    // 关闭按钮
    topRightCloseButton = new Image("images/panel/close.png", 0, 0, 15, 15);
    topRightCloseButton.setStyleName(STYLENAME_TOPRIGHTCLOSEBUTTON);
    topRightCloseButton.setTitle("关闭");
    TopRightCloseButtonHandler topRightCloseButtonHandler = new TopRightCloseButtonHandler();
    topRightCloseButton.addClickHandler(topRightCloseButtonHandler);
    topRightCloseButton.addMouseOverHandler(topRightCloseButtonHandler);
    topRightCloseButton.addMouseOutHandler(topRightCloseButtonHandler);
    captionBar.add(topRightCloseButton);
    captionBar.setCellHorizontalAlignment(topRightCloseButton, HasAlignment.ALIGN_RIGHT);
    captionBar.setCellVerticalAlignment(topRightCloseButton, HasAlignment.ALIGN_MIDDLE);

    // 内容框架，包含工作区域和按钮面板
    contentFrame = new VerticalPanel();
    contentFrame.setWidth("100%");
    contentFrame.setHeight("100%");
    root.setWidget(1, 1, contentFrame);
    root.getCellFormatter().setVerticalAlignment(1, 1, HasAlignment.ALIGN_TOP);

    //工作区域
    workingArea = new SimplePanel();
    contentFrame.add(workingArea);

    // 按钮面板，在buildButtons时才被加入到contentFrame中
    buttonsFrame = new HorizontalPanel();
    buttonsFrame.setWidth("100%");
    buttonsFrame.setStyleName(STYLENAME_BUTTONSFRAME);

    buttonsPanel = new HorizontalPanel();
    buttonsFrame.add(buttonsPanel);
    buttonsFrame.setCellHorizontalAlignment(buttonsPanel, HasAlignment.ALIGN_CENTER);

    super.setWidget(root);
  }

  /**
   * 标题文本
   * 
   * @return
   */
  public String getCaptionText() {
    return caption.getText();
  }

  /**
   * 设置标题文本
   * 
   * @param text
   */
  public void setCaptionText(String text) {
    // caption为空串会使得标题栏无法被拖动，所以需要避免
    if (text == null || text.equals(""))
      caption.setHTML("&nbsp");
    else
      caption.setText(text);
  }

  /**
   * 标题HTML
   * 
   * @return
   */
  public String getCaptionHTML() {
    return caption.getHTML();
  }

  /**
   * 设置标题HTML
   * 
   * @param html
   */
  public void setCaptionHTML(String html) {
    // caption为空串会使得标题栏无法被拖动，所以需要避免
    if (html == null || html.equals(""))
      html = "&nbsp;";
    caption.setHTML(html);
  }

  /**
   * 是否显示对话框右上角的关闭按钮
   * 
   * @return
   */
  public boolean isShowCloseButton() {
    return topRightCloseButton.isVisible();
  }

  /**
   * 设置是否显示对话框右上角的关闭按钮
   * 
   * @param showCloseButton
   */
  public void setShowCloseButton(boolean showCloseButton) {
    topRightCloseButton.setVisible(showCloseButton);
  }

  /**
   * 设置对话框工作区域的和边框之间的空隙宽度，默认为0
   * 
   * @param padding
   */
  public void setWorkingAreaPadding(int padding) {
    DOM.setStyleAttribute(workingArea.getElement(), "padding", new Integer(padding).toString()
        + "px");
  }

  /**
   * 按钮集合，没有按钮则返回null
   * 
   * @return
   */
  public ButtonConfig[] getButtons() {
    return buttonConfigs;
  }

  /**
   * 设置按钮集合
   * 
   * @param buttons
   *          传入null则清除所有按钮
   */
  public void setButtons(ButtonConfig[] buttons) {
    this.buttonConfigs = buttons;
    buildButtons();
  }

  /**
   * 构造按钮
   * 
   */
  private void buildButtons() {
    buttonMap.clear();

    if (buttonConfigs == null) {
      contentFrame.remove(buttonsFrame);
      return;
    }

    if (contentFrame.getWidgetCount() == 1) {
      contentFrame.add(buttonsFrame);
      contentFrame.setCellHeight(buttonsFrame, "32px");
    }

    buttonsPanel.clear();
    for (int i = 0; i < buttonConfigs.length; i++) {
      final ButtonConfig buttonConfig = buttonConfigs[i];

      final RButton button = new RButton(buttonConfig.getCaption());

      button.addClickHandler(new ClickHandler() {
        public void onClick(ClickEvent event) {
          clickedButton = buttonConfig;
          if (buttonClickListeners.size() == 0) {
            if (buttonConfig.isClickToClose())
              hide();
          } else {
            boolean hideDialog = buttonClickListeners.fireClick(RDialog.this, buttonConfig, button);
            if (hideDialog)
              hide();
          }
        }
      });
      button.setTabIndex(i);
      buttonsPanel.add(button);
      buttonsPanel.setCellWidth(button, "70px");

      buttonMap.put(buttonConfig, button);

      // 按钮之间的分隔
      if (i < buttonConfigs.length - 1) {
        HTML blank = new HTML("&nbsp");
        blank.setWidth("15px");
        buttonsPanel.add(blank);
      }
    }
  }

  /**
   * 是否显示下方按钮，要调用过setButtons按钮才会真的显示
   * 
   * @return
   */
  public boolean isShowButtons() {
    return buttonsFrame.isVisible();
  }

  /**
   * 设置是否显示下方按钮，要调用过setButtons按钮才会真的显示
   * 
   * @param showButtons
   */
  public void setShowButtons(boolean showButtons) {
    buttonsFrame.setVisible(showButtons);
  }

  /**
   * 根据按钮定义查找按钮
   * 
   * @param buttonConfig
   * @return
   */
  public RButton getButton(ButtonConfig buttonConfig) {
    if (buttonConfig == null || buttonConfigs == null || buttonConfigs.length == 0)
      return null;

    return (RButton) buttonMap.get(buttonConfig);
  }

  /**
   * 对话框显示时获得焦点的按钮，默认为null，表示没有任何按钮获得焦点
   * 
   * @return
   */
  public ButtonConfig getFocusedButton() {
    return focusedButton;
  }

  /**
   * 设置对话框显示时获得焦点的按钮，如果focusedButton不在setButtons中设置的按钮集合中，
   * 则不会产生任何作用
   * 
   * @param focusedButton
   */
  public void setFocusedButton(ButtonConfig focusedButton) {
    this.focusedButton = focusedButton;
  }

  /**
   * 被点击的按钮，若还没有按钮被点击则返回null
   * 
   * @return
   */
  public ButtonConfig getClickedButton() {
    return clickedButton;
  }

  /**
   * 添加RDialogButtonClickListener事件监听器
   * 
   * @param listener
   */
  public void addButtonClickListener(RDialogButtonClickListener listener) {
    buttonClickListeners.add(listener);
  }

  /**
   * 删除RDialogButtonClickListener事件监听器
   * 
   * @param listener
   */
  public void removeButtonClickListener(RDialogButtonClickListener listener) {
    buttonClickListeners.remove(listener);
  }

  /**
   * 添加RDialogListener事件监听器
   * 
   * @param listener
   * @deprecated 请用{@link #addCloseHandler()}和
   *             {@link #addOpenHandler()}代替
   */
  @Deprecated
  public void addListener(RDialogListener listener) {
    WrappedListener.add(this, listener);
  }

  /**
   * 删除RDialogListener事件监听器
   * 
   * @param listener
   * @deprecated 请用{@link #addCloseHandler()}和
   *             {@link #addOpenHandler()}返回的
   *             {@link HandlerRegistration#removeHandler}代替
   */
  @Deprecated
  public void removeListener(RDialogListener listener) {
    WrappedListener.remove(this, listener);
  }

  /**
   * 对话框显示前触发
   * 
   */
  public HandlerRegistration addOpenHandler(OpenHandler<PopupPanel> handler) {
    return addHandler(handler, OpenEvent.getType());
  }

  /**
   * 是否显示遮罩，默认为显示
   * 
   * @return
   */
  public boolean isShowMask() {
    return showMask;
  }

  /**
   * 设置是否显示遮罩
   * 
   * @param showMask
   */
  public void setShowMask(boolean showMask) {
    this.showMask = showMask;
  }

  /**
   * 是否支持用ESC键关闭对话框
   * 
   * @return
   */
  public boolean isEscKeyToHide() {
    return escKeyToHide;
  }

  /**
   * 设置是否支持用ESC键关闭对话框
   * 
   * @param escKeyToHide
   */
  public void setEscKeyToHide(boolean escKeyToHide) {
    this.escKeyToHide = escKeyToHide;
  }

  /**
   * 设置工作区域控件
   * 
   * @param widget
   */
  public void setWidget(Widget widget) {
    workingArea.setWidget(widget);
  }

  protected void onPreviewNativeEvent(NativePreviewEvent event) {
    NativeEvent nativeEvent = event.getNativeEvent();

    if (!event.isCanceled()) {
      if ((event.getTypeInt() == Event.ONMOUSEDOWN) && isCaptionEvent(nativeEvent)) {
        // We need to preventDefault() on mouseDown events (outside of the
        // DialogBox content) to keep text from being selected when it
        // is dragged.
        nativeEvent.preventDefault();
      } else if (event.getTypeInt() == Event.ONKEYDOWN && escKeyToHide
          && nativeEvent.getKeyCode() == KeyCodes.KEY_ESCAPE) {
        // 按ESC键关闭对话框
        clickedButton = null;
        hide();
      }
    }

    super.onPreviewNativeEvent(event);
  }

  private boolean isCaptionEvent(NativeEvent event) {
    EventTarget target = event.getEventTarget();
    if (Element.is(target)) {
      return caption.getElement().isOrHasChild(Element.as(target));
    }
    return false;
  }

  public void show() {
    OpenEvent.fire(this, this);

    // 显示遮罩
    if (showMask)
      RMask.show();

    // 显示对话框
    super.show();

    // 定位焦点
    if (focusedButton != null && buttonConfigs != null) {
      DeferredCommand.addCommand(new Command() {
        public void execute() {
          Focusable focus = null;
          for (int i = 0; i < buttonConfigs.length; i++) {
            if (focusedButton.equals(buttonConfigs[i])) {
              focus = (Focusable) buttonsPanel.getWidget(i);
              break;
            }
          }
          if (focus != null)
            focus.setFocus(true);
        }
      });
    }

    // 重置被点击按钮
    clickedButton = null;
  }

  public void hide() {
    super.hide();
    RMask.hide();
  }

  /**
   * 标题事件处理，允许鼠标拖动标题栏
   * 
   * @author LiQi
   * 
   */
  private class CaptionHandler implements MouseDownHandler, MouseMoveHandler, MouseUpHandler {

    private boolean dragging;
    private int dragStartX, dragStartY;

    public void onMouseDown(MouseDownEvent event) {
      dragging = true;
      DOM.setCapture(caption.getElement());
      dragStartX = event.getX();
      dragStartY = event.getY();
    }

    public void onMouseMove(MouseMoveEvent event) {
      if (dragging) {
        int absX = event.getX() + getAbsoluteLeft();
        int absY = event.getY() + getAbsoluteTop();
        setPopupPosition(absX - dragStartX, absY - dragStartY);
      }
    }

    public void onMouseUp(MouseUpEvent event) {
      dragging = false;
      DOM.releaseCapture(caption.getElement());
    }
  }

  /**
   * 右上角的关闭按钮的事件处理
   * 
   * @author LiQi
   * 
   */
  private class TopRightCloseButtonHandler implements ClickHandler, MouseOverHandler,
      MouseOutHandler {

    public void onClick(ClickEvent event) {
      hide();
    }

    public void onMouseOver(MouseOverEvent event) {
      ((Image) event.getSource()).setVisibleRect(15, 0, 15, 15);
    }

    public void onMouseOut(MouseOutEvent event) {
      ((Image) event.getSource()).setVisibleRect(0, 0, 15, 15);
    }
  }

  /**
   * 按钮定义
   * 
   * @author LiQi
   * 
   */
  public static class ButtonConfig {

    private String caption;

    /** 点击时是否关闭对话框 */
    private boolean clickToClose = true;

    public ButtonConfig(String caption) {
      this.caption = caption;
    }

    public ButtonConfig(String caption, boolean clickToClose) {
      this(caption);
      this.clickToClose = clickToClose;
    }

    public String getCaption() {
      return caption;
    }

    /**
     * 点击时是否关闭对话框，默认为是。只有当没有任何RDialogButtonClickListener存在时，
     * 本属性才会生效，否则就由 RDialogButtonClickListener决定是否关闭对话框。
     * 
     * @return
     */
    public boolean isClickToClose() {
      return clickToClose;
    }

    /**
     * 设置点击时是否关闭对话框
     * 
     * @param clickToClose
     */
    public void setClickToClose(boolean clickToClose) {
      this.clickToClose = clickToClose;
    }

  }

  @Deprecated
  public static class WrappedListener extends ListenerWrapper<RDialogListener> implements
      OpenHandler, CloseHandler {

    @Deprecated
    public static WrappedListener add(RDialog source, RDialogListener listener) {
      WrappedListener rtn = new WrappedListener(listener);
      source.addOpenHandler(rtn);
      source.addCloseHandler(rtn);
      return rtn;
    }

    @Deprecated
    public static void remove(Widget eventSource, RDialogListener listener) {
      baseRemove(eventSource, listener, OpenEvent.getType(), CloseEvent.getType());
    }

    private WrappedListener(RDialogListener listener) {
      super(listener);
    }

    public void onOpen(OpenEvent event) {
      getListener().beforeShow((RDialog) getSource(event));
    }

    public void onClose(CloseEvent event) {
      getListener().onPopupClosed((PopupPanel) getSource(event), event.isAutoClosed());
    }
  }
}
